#include "plugin.h"

#include <time.h>
#include <GL\glew.h>
#include <GL\wglew.h>

// --- forward declarations ---------------------------------------------------

void DrawParticle(Particle* node, int id, void* data);
void DrawParticleAlpha(Particle* node, int id);
void CopyToFinalBuffer(const LWFilterAccess* filteraAccess, const float* pixels, 
                       int width, int height, BOOL rgb, BOOL alpha);

// ----------------------------------------------------------------------------
// --- Buffer preparation and destruction -------------------------------------
// ----------------------------------------------------------------------------

// ----------------------------------------------------------------------------
BOOL PrepareBuffers(BWInstanceData* instance)
{
    BOOL result;
    float *r, *g, *b, *a, *z;
    int width, height, x, y;
    const LWFilterAccess* fa = instance->filterAccess;
    
    width = fa->width;
    height = fa->height;

    if (width == 0 || height == 0) 
    {
        WriteTrace("Width or height of image is zero -> aborting");
        return FALSE;
    }
    
    // prepare pixel buffers
    instance->pixelBuffer = calloc(width * height * 4, sizeof(float));
    instance->depthBuffer = calloc(width * height, sizeof(float));

    // copy data from last renderstep 
    for (y = 0; y < height; y++) 
    {
        // get each scanline
        r = fa->getLine(LWBUF_RED, y);
        g = fa->getLine(LWBUF_GREEN, y);
        b = fa->getLine(LWBUF_BLUE, y);
        a = fa->getLine(LWBUF_ALPHA, y);
        z = fa->getLine(LWBUF_DEPTH, y);
        
        for (x = 0; x < fa->width; x++) 
        {             
            instance->pixelBuffer[4*y*width + 4*x + 0] = r[x]; 
            instance->pixelBuffer[4*y*width + 4*x + 1] = g[x]; 
            instance->pixelBuffer[4*y*width + 4*x + 2] = b[x]; 
            instance->pixelBuffer[4*y*width + 4*x + 3] = a[x];
            instance->depthBuffer[  y*width + x      ] = z[x];
        }
    }

    checkGLerror();

    result = InitGlew(instance);
    if (!result) return FALSE;

    result = CreatePBuffer(fa->width, fa->height);
    if (!result) return FALSE;
    
    ActivatePBuffer();

    return TRUE;
}

// ----------------------------------------------------------------------------
void ReleaseBuffers(BWInstanceData* instance)
{
    free(instance->pixelBuffer);
    free(instance->depthBuffer);
    DeactivatePBuffer();
    DestroyPBuffer();
}

// ----------------------------------------------------------------------------
// --- OpenGL rendering -------------------------------------------------------
// ----------------------------------------------------------------------------

void DrawParticles(BWInstanceData* instance)
{
    int width, height;
    float *scratchBuffer;

    width = instance->filterAccess->width;
    height = instance->filterAccess->height;
    
    if (width == 0 || height == 0) 
    {
        WriteTrace("Width or height of image is zero -> aborting");
        return;
    }    
    
    SetupGLMatrices(instance->globalFunc, width, height);

    // --- define OpenGL settings ---

    glShadeModel(GL_FLAT);							    
    glClearColor(0.0f, 0.0f, 0.0f, 0.0f); // Black Background    
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); 
    glDisable(GL_CULL_FACE);
    glDisable(GL_LIGHTING);
    glClear(GL_COLOR_BUFFER_BIT);

    scratchBuffer = calloc(width * height * 4, sizeof(float));

    // --- render RGB part ---
    
    // draw last renderstep
    if (!instance->renderParticlesOnly)
        DrawPixels(width, height, GL_RGBA, instance->pixelBuffer);

    glEnable(GL_TEXTURE_2D);
    glEnable(GL_BLEND);

    // draw rgb
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 
    glEnable(GL_DEPTH_TEST); // z-Test enabled for non-painterly objects
    glDepthMask(GL_FALSE);   // ... - but z-Buffer is not modified!
    Iterate(instance->particleList, DrawParticle, instance);
    glFlush();

    glDisable(GL_BLEND);
    glDisable(GL_TEXTURE_2D);
    glDisable(GL_DEPTH_TEST);
    
    // disable anything we don't need
    //glDisable(GL_ALPHA_TEST);
    //glDisable(GL_DEPTH_TEST);
    //glDisable(GL_DITHER);
    //glDisable(GL_FOG);
    //glDisable(GL_LIGHTING);
    //glDisable(GL_LOGIC_OP);
    //glDisable(GL_STENCIL_TEST);
    //glDisable(GL_TEXTURE_1D);

    ReadPixels(0, 0, width, height, GL_RGBA, scratchBuffer);

    CopyToFinalBuffer(instance->filterAccess, scratchBuffer, 
                      width, height, TRUE, FALSE);

    // --- draw alpha part ---

    // again: draw last renderstep
    if (!instance->renderParticlesOnly)
        DrawPixels(width, height, GL_RGBA, instance->pixelBuffer);

    glEnable(GL_TEXTURE_2D);
    glEnable(GL_BLEND);

    // draw correct alpha values
    glBlendFunc(GL_SRC_COLOR, GL_ONE);
    glEnable(GL_DEPTH_TEST); // z-Test enabled for non-painterly objects
    glDepthMask(GL_FALSE);   // ... - but z-Buffer is not modified!
    Iterate(instance->particleList, DrawParticle, instance);
    glFlush();

    glDisable(GL_BLEND);
    glDisable(GL_TEXTURE_2D);
    glDisable(GL_DEPTH_TEST);

    ReadPixels(0, 0, width, height, GL_RGBA, scratchBuffer);

    CopyToFinalBuffer(instance->filterAccess, scratchBuffer, 
                      width, height, FALSE, TRUE);

    checkGLerror();

    free(scratchBuffer);
}

// ----------------------------------------------------------------------------
void DrawPixels(int width, int height, GLenum format, const float* pixels)
{
    glMatrixMode(GL_PROJECTION);
    glPushMatrix();    
        glLoadIdentity();
        gluOrtho2D(0.0, (GLfloat) width, 0.0, (GLfloat) height);

        glMatrixMode(GL_MODELVIEW);
        glPushMatrix();
            glLoadIdentity();
    
            glRasterPos2d(0, 0);
            glDrawPixels(width, height, format, GL_FLOAT, pixels);
        glPopMatrix();

    glMatrixMode(GL_PROJECTION);
    glPopMatrix();

    checkGLerror();
}

// ----------------------------------------------------------------------------
void ReadPixels(int x, int y, int width, int height, GLenum format, 
                float* pixels)
{
    glMatrixMode(GL_PROJECTION);
    glPushMatrix();    
        glLoadIdentity();
        gluOrtho2D(0.0, (GLfloat) width, 0.0, (GLfloat) height);

        glMatrixMode(GL_MODELVIEW);
        glPushMatrix();
            glLoadIdentity();
    
            glRasterPos2d(0, 0);
            glReadPixels(x, y, width, height, format, GL_FLOAT, pixels);
        glPopMatrix();
    
    glMatrixMode(GL_PROJECTION);
    glPopMatrix();
    
    checkGLerror();
}

// ----------------------------------------------------------------------------
void CopyToFinalBuffer(const LWFilterAccess* filterAccess,
                       const float* pixels, int width, int height, 
                       BOOL rgb, BOOL alpha)
{
    int x, y;

    for (x=0; x<width; ++x)
    {
        for (y=0; y<height; ++y)
        {
            const float *color = &(pixels[(y*width + x)*4]);
            if (rgb)   filterAccess->setRGB((int) x, (int) y, color);
            if (alpha) filterAccess->setAlpha((int) x, (int) y, color[3]);            
        }
    }
}

// ----------------------------------------------------------------------------
void DrawParticle(Particle* node, int id, void* data)
{    
    int i;
    float size;
    double up[3], right[3], scaledUp[3], scaledRight[3], 
        scaledDown[3], scaledLeft[3], tmp[3], partPos[3], partNormal[3];
    LWTime time;
    LWItemID camID;
    BWInstanceData* instance = (BWInstanceData*) data;

    const LWItemInfo* itemInfo = getLWItemInfo(instance->globalFunc);
    const LWSceneInfo* sceneInfo = getLWSceneInfo(instance->globalFunc);
    const LWCameraInfo* camInfo = getLWCameraInfo(instance->globalFunc);
    const LWTimeInfo* timeInfo = getLWTimeInfo(instance->globalFunc);

    for (i=0; i<3; ++i) 
    {
        partPos[i] = (float) node->pos[i];
        partNormal[i] = (float) node->normal[i];
    }

    time = timeInfo->time;    
    camID = sceneInfo->renderCamera(time);
    itemInfo->param(camID, LWIP_RIGHT, time, right);
    itemInfo->param(camID, LWIP_UP,    time, up);

    if (node->align)
    {
        // partR = camUP x partN
        cross(right, up, partNormal);
        normalize(right, right);        

        // partU = partR x partN
        cross(up, right, partNormal);
        normalize(up, up);
    }
    
    size = (float) (node->size * node->zDist / 2.0);
    multiply(scaledRight, right, size * node->aspectRatio);
    multiply(scaledUp, up, size);    
    multiply(scaledLeft, scaledRight, -1.0);
    multiply(scaledDown, scaledUp, -1.0);

    //WriteTrace("Drawing particle %d: %f, %f, %f", 
    //    id, node->pos[0], node->pos[1], node->pos[2]);
    
    if (!node->textureInfo->isLoaded || 
        !glIsTexture(node->textureInfo->glTextureID))
    {
        LoadTexture(instance, node->textureInfo);
    }
    
    glPushMatrix();
        
    glColor4f(node->colour[0], 
              node->colour[1], 
              node->colour[2], 
              node->colour[3]);
    
    glBindTexture(GL_TEXTURE_2D, node->textureInfo->glTextureID);

    glBegin (GL_QUADS);
		
        add(tmp, scaledDown, scaledLeft);
        add(tmp, partPos, tmp);    
        glTexCoord2f(0.0f, 0.0f); glVertex3dv(tmp);
		
        add(tmp, scaledUp, scaledLeft);
        add(tmp, partPos, tmp);   
        glTexCoord2f(0.0f, 1.0f); glVertex3dv(tmp);
		
        add(tmp, scaledUp, scaledRight);
        add(tmp, partPos, tmp);   
        glTexCoord2f(1.0f, 1.0f); glVertex3dv(tmp);
		
        add(tmp, scaledDown, scaledRight);
        add(tmp, partPos, tmp);   
        glTexCoord2f(1.0f, 0.0f); glVertex3dv(tmp);	

	glEnd();

    glBindTexture(GL_TEXTURE_2D, 0);
    
    glPopMatrix();

    checkGLerror();
}
